package com.ak.game.netty;

import cn.hutool.core.util.ObjectUtil;
import com.ak.game.entity.BettingRecord;
import com.ak.game.entity.PnGameRecord;
import com.ak.game.entity.PnUserStream;
import com.ak.game.entity.PnVipUser;
import com.ak.game.entity.vo.BetRecordRspVo;
import com.ak.game.entity.vo.BetReqVo;
import com.ak.game.service.*;
import com.ak.game.util.AjaxResult;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Slf4j
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private final String gameId;

    private final RemoteWebSocketClientService clientService;

    private ChannelHandlerContext ctx;

    private final BettingService bettingService;

    private final PnVipUserService vipUserService;

    private final PnUserStreamService userStreamService;

    private final PnGameRecordService gameRecordService;

    public WebSocketFrameHandler(String gameId, RemoteWebSocketClientService clientService, BettingService bettingService, PnVipUserService vipUserService, PnUserStreamService userStreamService, PnGameRecordService gameRecordService) {
        this.gameId = gameId;
        this.clientService = clientService;
        this.bettingService = bettingService;
        this.vipUserService = vipUserService;
        this.userStreamService = userStreamService;
        this.gameRecordService = gameRecordService;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
        super.channelActive(ctx);
    }

    /**
     * 重写SimpleChannelInboundHandler的channelRead0方法，处理接收到的消息
     *
     * @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
     *            belongs to
     * @param msg the message to handle
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        String text = msg.text();
        JSONObject jsonObject = JSONObject.parseObject(text);
        String msgId = jsonObject.getString("msg_id");
        Map<String, List<String>> params = (Map<String, List<String>>) ctx.channel().attr(AttributeKey.valueOf("params")).get();
        String userId = params.get("user_id").get(0);
        String gameTime = jsonObject.get("game_time").toString();
        Map<String, Object> map = new HashMap<>();
        switch (msgId) {
            // 下注请求
            case "1004":
                log.info("收到msg_id=1004下注请求，请求参数={}", text);
                // 验证是否是下注阶段
                if (!GameScheduler1.IS_BETTING_PHASE && gameTime.equals("1")) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10008", "1004"))));
                    return;
                }
                if (!GameScheduler3.IS_BETTING_PHASE && gameTime.equals("3")) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10008", "1004"))));
                    return;
                }
                if (!GameScheduler5.IS_BETTING_PHASE && gameTime.equals("5")) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10008", "1004"))));
                    return;
                }
                if (!GameScheduler10.IS_BETTING_PHASE && gameTime.equals("10")) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10008", "1004"))));
                    return;
                }
                // 组装数据给接口api服务端
                map.put("msg_id", "100");
                Map<String, String> dataMap = new HashMap<>();
                dataMap.put("user_id", userId);
                dataMap.put("amount", jsonObject.getString("amount"));
                dataMap.put("type", jsonObject.getString("type"));
                Map<String, Object> gameMap = new HashMap<>();
                gameMap.put("id", gameId);
                if (gameTime.equals("1")) {
                    gameMap.put("round_id", GameScheduler1.ROUND_ID);
                    gameMap.put("start_time", GameScheduler1.START_TIME);
                } else if (gameTime.equals("3")) {
                    gameMap.put("round_id", GameScheduler3.ROUND_ID);
                    gameMap.put("start_time", GameScheduler3.START_TIME);
                } else if (gameTime.equals("5")) {
                    gameMap.put("round_id", GameScheduler5.ROUND_ID);
                    gameMap.put("start_time", GameScheduler5.START_TIME);
                } else if (gameTime.equals("10")) {
                    gameMap.put("round_id", GameScheduler10.ROUND_ID);
                    gameMap.put("start_time", GameScheduler10.START_TIME);
                }
                map.put("data", dataMap);
                map.put("game", gameMap);
                // 处理下注业务逻辑
                ctx.channel().attr(AttributeKey.valueOf("userId")).set(userId);
                BetReqVo betReqVo = new BetReqVo().setBetType(jsonObject.getInteger("type")).setAmount(jsonObject.getBigDecimal("amount")).setUserId(userId).setMsgId(msgId).setGameTime(gameTime);
                if (Objects.equals(gameTime, "1")) {
                    // 验证重复下注
                    if (ObjectUtil.isNotNull(BettingRecord.bets1.get(betReqVo))) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10009", "1004"))));
                        return;
                    }
                    CompletableFuture<Object> objectCompletableFuture = clientService.sendMessage(JSONObject.toJSONString(map));
                    try {
                        Object betResult = objectCompletableFuture.get(10, TimeUnit.SECONDS);
                        AjaxResult betResult1 = (AjaxResult) betResult;
                        betResult1.setMsg_id("1004");
                        if (betResult1.getCode() == 200) {
                            bettingService.asyncPlaceBet1(betReqVo, ctx);
                            GameScheduler1.USER_CTX_MAP.put(userId, ctx);
                            GameScheduler3.USER_CTX_MAP.remove(userId);
                            GameScheduler5.USER_CTX_MAP.remove(userId);
                            GameScheduler10.USER_CTX_MAP.remove(userId);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(betResult1)));
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        RemoteWebSocketClientService.FUTURES.remove(userId);
                        throw new RuntimeException(e);
                    }
                } else if (Objects.equals(gameTime, "3")) {
                    // 验证重复下注
                    if (ObjectUtil.isNotNull(BettingRecord.bets3.get(betReqVo))) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10009", "1004"))));
                        return;
                    }
                    CompletableFuture<Object> objectCompletableFuture = clientService.sendMessage(JSONObject.toJSONString(map));
                    try {
                        Object betResult = objectCompletableFuture.get(10, TimeUnit.SECONDS);
                        AjaxResult betResult1 = (AjaxResult) betResult;
                        betResult1.setMsg_id("1004");
                        if (betResult1.getCode() == 200) {
                            bettingService.asyncPlaceBet3(betReqVo, ctx);
                            GameScheduler3.USER_CTX_MAP.put(userId, ctx);
                            GameScheduler1.USER_CTX_MAP.remove(userId);
                            GameScheduler5.USER_CTX_MAP.remove(userId);
                            GameScheduler10.USER_CTX_MAP.remove(userId);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(betResult1)));
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        RemoteWebSocketClientService.FUTURES.remove(userId);
                        throw new RuntimeException(e);
                    }
                } else if (Objects.equals(gameTime, "5")) {
                    // 验证重复下注
                    if (ObjectUtil.isNotNull(BettingRecord.bets5.get(betReqVo))) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10009", "1004"))));
                        return;
                    }
                    CompletableFuture<Object> objectCompletableFuture = clientService.sendMessage(JSONObject.toJSONString(map));
                    try {
                        Object betResult = objectCompletableFuture.get(10, TimeUnit.SECONDS);
                        AjaxResult betResult1 = (AjaxResult) betResult;
                        betResult1.setMsg_id("1004");
                        if (betResult1.getCode() == 200) {
                            bettingService.asyncPlaceBet5(betReqVo, ctx);
                            GameScheduler5.USER_CTX_MAP.put(userId, ctx);
                            GameScheduler1.USER_CTX_MAP.remove(userId);
                            GameScheduler3.USER_CTX_MAP.remove(userId);
                            GameScheduler10.USER_CTX_MAP.remove(userId);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(betResult1)));
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        RemoteWebSocketClientService.FUTURES.remove(userId);
                        throw new RuntimeException(e);
                    }
                } else if (Objects.equals(gameTime, "10")) {
                    // 验证重复下注
                    if (ObjectUtil.isNotNull(BettingRecord.bets10.get(betReqVo))) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(AjaxResult.fail("10009", "1004"))));
                        return;
                    }
                    CompletableFuture<Object> objectCompletableFuture = clientService.sendMessage(JSONObject.toJSONString(map));
                    try {
                        Object betResult = objectCompletableFuture.get(10, TimeUnit.SECONDS);
                        AjaxResult betResult1 = (AjaxResult) betResult;
                        betResult1.setMsg_id("1004");
                        if (betResult1.getCode() == 200) {
                            bettingService.asyncPlaceBet10(betReqVo, ctx);
                            GameScheduler10.USER_CTX_MAP.put(userId, ctx);
                            GameScheduler1.USER_CTX_MAP.remove(userId);
                            GameScheduler3.USER_CTX_MAP.remove(userId);
                            GameScheduler5.USER_CTX_MAP.remove(userId);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(betResult1)));
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        RemoteWebSocketClientService.FUTURES.remove(userId);
                        throw new RuntimeException(e);
                    }
                }
                break;
            case "1006":
                log.info("收到msg_id=1006切换订阅请求，请求参数={}", text);
                switch (gameTime) {
                    case "1":
                        GameScheduler1.USER_CTX_MAP.put(userId, ctx);
                        GameScheduler3.USER_CTX_MAP.remove(userId);
                        GameScheduler5.USER_CTX_MAP.remove(userId);
                        GameScheduler10.USER_CTX_MAP.remove(userId);
                        Map<String, Object> game1 = Map.of("game", GameScheduler1.gameRecordList);
                        Map<String, Object> gameStatus1;
                        if (GameScheduler1.IS_BETTING_PHASE) {
                            gameStatus1 = Map.of("betCountdown", GameScheduler1.REMAINING_TIME_IN_SECONDS, "drawCountdown", 10);
                        } else {
                            gameStatus1 = Map.of("betCountdown", 0, "drawCountdown", GameScheduler1.REMAINING_TIME_IN_SECONDS);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(game1, "1001"))));
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(gameStatus1, "1002"))));
                        break;
                    case "3":
                        GameScheduler3.USER_CTX_MAP.put(userId, ctx);
                        GameScheduler1.USER_CTX_MAP.remove(userId);
                        GameScheduler5.USER_CTX_MAP.remove(userId);
                        GameScheduler10.USER_CTX_MAP.remove(userId);
                        Map<String, Object> game3 = Map.of("game", GameScheduler3.gameRecordList);
                        Map<String, Object> gameStatus3;
                        if (GameScheduler3.IS_BETTING_PHASE) {
                            gameStatus3 = Map.of("betCountdown", GameScheduler3.REMAINING_TIME_IN_SECONDS, "drawCountdown", 20);
                        } else {
                            gameStatus3 = Map.of("betCountdown", 0, "drawCountdown", GameScheduler3.REMAINING_TIME_IN_SECONDS);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(game3, "1001"))));
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(gameStatus3, "1002"))));
                        break;
                    case "5":
                        GameScheduler5.USER_CTX_MAP.put(userId, ctx);
                        GameScheduler1.USER_CTX_MAP.remove(userId);
                        GameScheduler3.USER_CTX_MAP.remove(userId);
                        GameScheduler10.USER_CTX_MAP.remove(userId);
                        Map<String, Object> game5 = Map.of("game", GameScheduler5.gameRecordList);
                        Map<String, Object> gameStatus5;
                        if (GameScheduler5.IS_BETTING_PHASE) {
                            gameStatus5 = Map.of("betCountdown", GameScheduler5.REMAINING_TIME_IN_SECONDS, "drawCountdown", 30);
                        } else {
                            gameStatus5 = Map.of("betCountdown", 0, "drawCountdown", GameScheduler5.REMAINING_TIME_IN_SECONDS);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(game5, "1001"))));
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(gameStatus5, "1002"))));
                        break;
                    case "10":
                        GameScheduler10.USER_CTX_MAP.put(userId, ctx);
                        GameScheduler1.USER_CTX_MAP.remove(userId);
                        GameScheduler3.USER_CTX_MAP.remove(userId);
                        GameScheduler5.USER_CTX_MAP.remove(userId);
                        Map<String, Object> game10 = Map.of("game", GameScheduler10.gameRecordList);
                        Map<String, Object> gameStatus10;
                        if (GameScheduler10.IS_BETTING_PHASE) {
                            gameStatus10 = Map.of("betCountdown", GameScheduler10.REMAINING_TIME_IN_SECONDS, "drawCountdown", 60);
                        } else {
                            gameStatus10 = Map.of("betCountdown", 0, "drawCountdown", GameScheduler10.REMAINING_TIME_IN_SECONDS);
                        }
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(game10, "1001"))));
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(gameStatus10, "1002"))));
                        break;
                }
                break;
            case "1007":
                // 用户历史下注记录数据
                PnVipUser vipUser = vipUserService.getById(params.get("user_id").get(0));
                LambdaQueryWrapper<PnUserStream> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(PnUserStream::getGameId, "LuckyColor");
                queryWrapper.eq(PnUserStream::getExternalPlayerId, vipUser.getBinding());
                queryWrapper.orderByDesc(PnUserStream::getStartTime);
                queryWrapper.last("limit 30");
                List<PnUserStream> pnUserStreams = userStreamService.getBaseMapper().selectList(queryWrapper);
                List<BetRecordRspVo> betRecordRspVos = new ArrayList<>();
                for (PnUserStream pnUserStream : pnUserStreams) {
                    String recordId = pnUserStream.getPlaySessionId().substring(11, 24);
                    LambdaQueryWrapper<PnGameRecord> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                    lambdaQueryWrapper.eq(PnGameRecord::getRecordId, recordId);
                    BetRecordRspVo betRecordRspVo = new BetRecordRspVo();
                    PnGameRecord gameRecord = gameRecordService.getOne(lambdaQueryWrapper);
                    String playSessionId = pnUserStream.getPlaySessionId();
                    int index = playSessionId.indexOf(vipUser.getBinding());
                    String type = playSessionId.substring(index + vipUser.getBinding().length());
                    if (ObjectUtil.isNull(gameRecord)) {
                        betRecordRspVo.setIsDraw(1);
                        betRecordRspVo.setWin(BigDecimal.ZERO);
                        BetReqVo betReqVo1007 = new BetReqVo().setBetType(Integer.valueOf(type)).setAmount(pnUserStream.getBet()).setUserId(vipUser.getId()).setMsgId("1004");
                        for (Map.Entry<BetReqVo, ChannelHandlerContext> entry : BettingRecord.bets1.entrySet()) {
                            if (entry.getKey().getUserId().equals(userId) && String.valueOf(entry.getKey().getBetType()).equals(type) && entry.getKey().getMsgId().equals("1004") && entry.getKey().getAmount().compareTo(pnUserStream.getBet()) == 0) {
                                betRecordRspVo.setGameTime("1");
                                break;
                            }
                        }
                        for (Map.Entry<BetReqVo, ChannelHandlerContext> entry : BettingRecord.bets3.entrySet()) {
                            if (entry.getKey().getUserId().equals(userId) && String.valueOf(entry.getKey().getBetType()).equals(type) && entry.getKey().getMsgId().equals("1004") && entry.getKey().getAmount().compareTo(pnUserStream.getBet()) == 0) {
                                betRecordRspVo.setGameTime("3");
                                break;
                            }
                        }
                        for (Map.Entry<BetReqVo, ChannelHandlerContext> entry : BettingRecord.bets5.entrySet()) {
                            if (entry.getKey().getUserId().equals(userId) && String.valueOf(entry.getKey().getBetType()).equals(type) && entry.getKey().getMsgId().equals("1004") && entry.getKey().getAmount().compareTo(pnUserStream.getBet()) == 0) {
                                betRecordRspVo.setGameTime("5");
                                break;
                            }
                        }
                        for (Map.Entry<BetReqVo, ChannelHandlerContext> entry : BettingRecord.bets10.entrySet()) {
                            if (entry.getKey().getUserId().equals(userId) && String.valueOf(entry.getKey().getBetType()).equals(type) && entry.getKey().getMsgId().equals("1004") && entry.getKey().getAmount().compareTo(pnUserStream.getBet()) == 0) {
                                betRecordRspVo.setGameTime("10");
                                break;
                            }
                        }
                    } else {
                        betRecordRspVo.setIsDraw(0);
                        betRecordRspVo.setDrawType(gameRecord.getType());
                        betRecordRspVo.setWin(pnUserStream.getWin().add(pnUserStream.getBet()));
                        betRecordRspVo.setRoundNo(gameRecord.getRoundNo());
                        betRecordRspVo.setGameTime(gameRecord.getGameTime());
                    }
                    betRecordRspVo.setType(type);
                    betRecordRspVo.setBet(pnUserStream.getBet());
                    betRecordRspVo.setStartTime(pnUserStream.getStartTime());
                    betRecordRspVos.add(betRecordRspVo);
                }
                ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(AjaxResult.success(betRecordRspVos, "1007"))));
                break;
        }
    }

    /**
     * 管道异常处理，防止资源泄露
     *
     * @param ctx   通道代理上下文
     * @param cause 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        String userId = ctx.channel().attr(AttributeKey.valueOf("userId")).get().toString();
        RemoteWebSocketClientService.FUTURES.remove(userId);
        log.error("连接失败：{}", cause.toString()); // 记录日志
        ctx.close(); // 关闭发生异常的通道，防止资源泄露
    }


}
